!pr1
Reading DOS 3.3 Disks With ProDOS..........Bob Sander-Cederlof

At the track and sector level, DOS 3.3 disks are identical to ProDOS disks.  They both have 35 tracks, 16 sectors, and the sectors are laid out on the tracks the same way in both systems.  You can use DOS's COPYA program to copy ProDOS disks, and you can use some ProDOS utilities on DOS disks.

The structure of the files is of course entirely different between the two systems.  Hence the need for the CONVERT program found on ProDOS system master disks, and the System Utilities Disk that comes with the //c.  Unfortunately both of the above programs have bugs that get in the way nearly every time I want to move a file from DOS to ProDOS.  The one that bites me the most is the way CONVERT dies when it encounters a DOS filename which does not start with a letter.  We routinely use such "illegal" filenames on our disks to separate and identify sections of long catalogs, but CONVERT goes absolutely crazy when it finds one.

Therefore, I decided to write a program which could "LOAD" assembler source files from a DOS 3.3 disk while I am running the ProDOS version of the S-C Macro Assembler.  Even with error messages and other fancy features, the program turns out to be only a little over $280 bytes long, and it works.

It is based on the fact that the Block Read MLI call does not care whether the disk being read is a DOS or a ProDOS disk.  The Block Read MLI call reads 512 bytes, or two sectors, at a time.  The call looks like this:

       JSR $BF00       (MLI link in global page)
       .DA #$80        (block read code)
       .DA PARMLIST    (address of parameters)

MLI returns with carry clear if there was no error, or carry set if there was an error.  The error code will be in the A-register if there was an error.

The PARMLIST for Block Read looks like this:

    PARMLIST .DA #3         (3 parameters)
             .DA #$60       (1-byte unit number)
             .DA BUFFER     (address of 512-byte buffer)
             .DA 2          (2-byte block number)

Page 3-17 of "Beneath Apple ProDOS" contains a table which converts block numbers to physical track/sector, and vice versa.  The latest printing of the book also includes a line which correlates the physical sector values to the DOS 3.3 logical sector.  Boiling it down, you can derive a ProDOS block number from the DOS 3.3 logical sector by multiplying the track number by 8 and adding a value according to the sector number from the following table:

    DOS sector #:  0 1 2 3 4 5 6 7 8 9 A B C D E F
                   0 7 6 6 5 5 4 4 3 3 2 2 1 1 0 F

For example, track 0 sector 2 is in ProDOS block 6.  The only problem is, so is DOS track 0 sector 3.  We also need to remember whether a given sector is in the upper or lower half of a 512-byte block.

I developed the following subroutine, which will translate the DOS logical track and sector numbers into the appropriate block number, read the block, and return with the address of the buffer page in which the sector data has been read.  Call the routine with the track number in the A-register and the sector number in the X-register.  The high-byte of the buffer address will return in the X-register.  If MLI detects an error, the subroutine will return with carry set.
!lm+3

RTS    LDY #0  ASSUME BLOCK # < $100
       ASL     FORM TRACK*8
       ASL
       ASL
       BCC .1  ...BLOCK < $100
       INY     ...BLOCK > $0FF
.1     ASL     *2, MAKE ROOM FOR H/L FLAG BIT
       ORA BLKTBL,X   MERGE FROM SECTOR TRANSLATION
       ROR     H/L FLAG BIT TO CARRY
       STA BLOCK
       STY BLOCK+1
       LDX /BLOCK.BUFFER   HIGH BYTE OF BUFFER ADDRESS
       BCC .2  ...LOWER HALF OF BUFFER
       INX     ...UPPER HALF OF BUFFER
.2     JSR $BF00
       .DA #$80,PARMLIST
       RTS

BLKTBL .HS 00.0E.0D.0C.0B.0A.09.08
       .HS 07.06.05.04.03.02.01.0F

PARMLIST
       .DA #3
       .DA #$60        SLOT 6, DRIVE 1
       .DA BLOCK.BUFFER
BLOCK  .DA 0           <FILLED IN>
!lm-3

After playing with the subroutine a while, I proceeded to write the load program.  Using a well-worn copy of "Beneath Apple DOS", I figured out once more how to work through a DOS catalog.  I decided to display a menu of files on the screen, and allow a single keystroke to select a file to be loaded.

The program that follows is designed to work with the ProDOS version of the S-C Macro Assembler.  Assuming it has been assembled and is in a ProDOS binary file as DOS.LOAD, and assuming you have booted the ProDOS version of the S-C Macro Assembler, you can start up the load program by typing "-DOS.LOAD".  It will load source files from DOS disks, which are DOS type I files, and place them in the assembler's edit area.  After selecting the slot and drive, the program reads the DOS catalog and displays 20 filenames at a time.  Only type I filenames are displayed, any others are skipped over.  If there are more than 20 files, you can page through them.  If you change your mind about loading a file, you can abort.  If you see the file you want to load, you type a single letter to select it.  A few seconds later it has been loaded, and you are returned to the assembler.

The assembler's soft entry point is at $8003, and the load program jumps there after finishing a load or after encountering an error.  Three pointer locations in page zero which the assembler uses are used by the load program:  HIMEM ($73,74) points one byte higher than the program can be loaded; PP ($CA,CB) will point to the beginning of the program, if it is successfully loaded; LOMEM ($67,68) points to the lowest address the program can occupy.  HIMEM is normally at $7400, and LOMEM at $1000, but these can be changed with the HIMEM and LOMEM commands.  LOMEM could be set as low as $0800.

With these limitations on the program extent ($0800...73FF), you can see that the maximum size assembler source file that can be loaded from a DOS disk is $6C00 bytes, or 108 sectors.  Or, if you prefer to leave LOMEM at $1000, you can load $6400 bytes or 100 sectors.  Most likely you do not have any source files which are bigger than that anyway.  If you do, you need to load the DOS version of the assembler and split the files before they can be transferred to ProDOS.  The maximum size file of 108 data sectors would only have one track/sector list, so I did not include any logic to chain to a second track/sector list.  You may be wondering where the load program itself loads....

The command interpreter I developed for the ProDOS version of the S-C Macro Assembler has three 1024-byte buffers permanently allocated between $7400 and $7FFF.  None of them will be in use while the load program is executing, so I borrowed some of that space for the load program.  The load program itself loads inside the buffer space allocated to the EXEC command, at $7400-77FF.  The blocks read by MLI will be stored at $7C00-7DFF, and I will save a copy of the track/sector list for the file being loaded at $7E00-7EFF.

Now for a description of the actual code.  Lines 1270-1410 ask you to type in the slot and drive numbers of the floppy drive the DOS disk is in.  ProDOS uses a "unit number", which is a coded form of the slot and drive all in one byte.  The slot number is in bits 4-6, and the drive number (0 or 1, corresponding to drives 1 or 2 respectively) in bit 7.  My subroutine GETNUM prints a prompt message (selected by the Y-register), inputs a single character from the keyboard, and checks it for legal range.  GETNUM is designed to accept only digits, starting with "1", and up to but not including the value in the A-register when GETNUM is called.

Once the unit number has been established, we fall into the LOAD.MENU code.  This code is somewhat convoluted, enough to disgust even me.  Interlocking loops?  Multiple entries and exits?  Ouch!  Maybe it really IS structured code, but just not in Euclidean space.  I think maybe it could be diagrammed on the surface of a Klein bottle (recursive torus?).

Anyway, let's walk through it.  Line 1440-1500 set up a fresh menu display and read in the DOS VTOC page so we can start reading the catalog.  The second and third bytes in the VTOC page give the track and sector of the first catalog sector.  This is almost always track $11, sector $0F; however, by starting at VTOC, we are a little more general.  We are still assuming we know where the VTOC is, which is track $11, sector 0.  Some non-standard software sets up disks with the VTOC somewhere else, but you are very unlikely to find any S-C source code on such a disk.  Each sector of the catalog also contains the track/sector of the next catalog sector in the 2nd and 3rd bytes.

Lines 1530-1550 read in the next catalog sector and set the pointer to the first file entry in that sector.  Each file entry is 35 bytes long, and the first one starts at $0B within the sector.  The subroutine READ.NEXT.CATALOG.SECTOR will return with carry set if there are no more catalog sectors.  The first time through this code, when we fall in from the code above, we will read the first catalog sector.

Lines 1570-1960 pick up filenames out of the catalog sectors and write them on the screen.  Not all file names are used:  line 1610 filters out deleted files; lines 1660-1700 filter out files which are not type I.  The track and sector of the active type-I files are saved in an array, indexed by the menu letter.  These values are first picked up in lines 1620-1650, and added to the array in lines 1870-1940.  Lines 1720-1770 print the menu letter and two dashes, and then lines 1780-1850 print the filename.

Lines 1950-1960 decrement the line count and test if the screen is full yet.  I arbitrarily call a screen full if it has 20 filenames, leaving room for my three-line prompt message.  We jump to MENU.SELECTION when we reach 20 lines or when we reach the end of the catalog, whichever comes first.

If we are not yet at the end of catalog and have not yet filled the screen, or if the file was one that got filtered out of the menu, we come to GET.NEXT.FILE at line 1980.  Lines 1990-2040 update the pointer into the catalog sector so that it points at the next file, if there is another one.  If so, we branch back to NEXT.FILE.NAME, to try the next one in the current sector.  If no more names in this sector, we go back to NEXT.CAT.SECTOR to get the next catalog sector (if any).

When we reach the end of catalog, lines 2070,2080 set a flag.  We need a flag to tell whether it was screen-full or catalog- end which caused us to come to MENU.SELECTION, so we can either continue through the catalog or wrap-around to the beginning should you wish to see another screenful of filenames.

The MENU.SELECTION section prints a three-line prompt message and waits for you to type a character.  If you type a space, you seethe next screenful of filenames.  (Of course, if there are fewer than 21 type I files on the disk you will see the same ones over again.)  If you type the RETURN or ESCAPE keys, the load program will abort, returning directly to the assembler without loading a file.  If you type a letter in the range of the menu, that file will be loaded.  Any other key is ignored.

Lines 2260-2370 convert the menu letter you typed into an index to get the track and sector for the track/sector list of the selected file.  The track/sector list contains the track and sector for every data sector in the file.  Line 2310 reads the track/sector list, and lines 2330-2370 copy it into a special buffer.

The first two bytes of the first data sector of a type-I file contain the length of the file.  We need to know the length so we can figure out where to read the data.  Lines 2390-2510 read in the first data sector and get the file size.

Lines 2520-2630 figure out where PP should be set so that the file exactly fits between PP and HIMEM, and checks to make sure that it does not go below LOMEM.

Lines 2650-2670 copy the rest of that first sector into the load area, starting at PP.  If the file is so short it doesn't fill the first data sector, the LOAD.FROM.SECTOR subroutine will return with carry set and we will return to the assembler, all finished.  Otherwise, we fall into the code below, to load the succeeding data sectors.  Eventually we will bump into HIMEM, and we are finished.

Now that this program is working I can see neat ways to extend it.  Why restrict it to type-I files?  It could also BLOAD type-B files, as long as an appropiate load address was set up.  It could do the equivalent of a BLOAD on a type-T file, which then could be BSAVE as type TXT in ProDOS.  Seems like we might be able to do away with the need for CONVERT, at least in the direction of moving from DOS to ProDOS.
